package com.me.common.util.security;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
import java.util.Map;

/**
 * Java 数字签名工具类：先用摘要算法计算摘要（MD5、SHA256...），再用加密算法加密摘要（RSA、AES...）
 *
 * <ul>
 * <li>
 * SHA256withRSA：SHA256 计算摘要，然后 RSA私钥 计算签名
 *
 * <li>
 * MD5withRSA：MD5 计算摘要，然后 RSA私钥 计算签名
 *
 * @author ME
 * @date 2021/8/13
 */
public class SignatureUtil {

    /** 预设的签名算法枚举 */
    public static final String MD5_WITH_RSA_SIGNATURE = "MD5withRSA";
    public static final String SHA1_WITH_RSA_SIGNATURE = "SHA1withRSA";
    public static final String SHA256_WITH_RSA_SIGNATURE = "SHA256withRSA";

    /** 选择的 签名算法 */
    public static final String SELECTED_ALGORITHM = SHA256_WITH_RSA_SIGNATURE;

    private static final Base64.Decoder DECODER64 = Base64.getDecoder();
    private static final Base64.Encoder ENCODER64 = Base64.getEncoder();

    public static void main(String[] args) throws Exception {
        String originalStr = "这是一行需要加密的原始内容";
        Map<String, Key> keyPair = RSAUtil.genKeyPair();
        String privateKey = RSAUtil.getKeyStr(keyPair.get(RSAUtil.PRIVATE_KEY));
        String publicKey = RSAUtil.getKeyStr(keyPair.get(RSAUtil.PUBLIC_KEY));

        // 用私钥对信息生成数字签名
        System.out.println("数字签名：");
        String sign = SignatureUtil.sign(originalStr, privateKey);
        System.out.println(sign);

        // 使用公钥进行签名校验
        System.out.println("\n签名校验结果：");
        boolean verify = SignatureUtil.verifySign(originalStr, publicKey, sign);
        System.out.println(verify);
    }

    /**
     * 用私钥对信息加密生成数字签名
     *
     * @param plaintext     需要生成数字签名的源数据
     * @param privateKey    私钥（Base64 编码）
     */
    public static String sign(String plaintext, String privateKey) throws Exception {
        return sign(plaintext.getBytes(StandardCharsets.UTF_8), privateKey);
    }

    /**
     * 用私钥对信息加密生成数字签名
     *
     * @param plainBytes        需要生成数字签名的源数据
     * @param privateKey        私钥（Base64 编码）
     */
    public static String sign(byte[] plainBytes, String privateKey) throws Exception {
        // 使用密钥规范（密钥材料）生成私钥对象
        PrivateKey priKey = RSAUtil.getPrivateKey(privateKey);
        // 基于指定签名算法，生成数字签名工具类
        Signature signature = Signature.getInstance(SELECTED_ALGORITHM);
        // 初始化私钥
        signature.initSign(priKey);
        // 设置需要签名的数据
        signature.update(plainBytes);
        // 获得签名值（bytes类型）
        byte[] sign = signature.sign();

        // 签名值转换成为 base64格式（String类型）
        return ENCODER64.encodeToString(sign);
    }

    /**
     * 校验数字签名
     *
     * @param originalStr   需要生成数字签名的源数据
     * @param publicKey     公钥（Base64 编码）
     * @param sign          数字签名
     */
    public static boolean verifySign(String originalStr, String publicKey, String sign) throws Exception {
        return verifySign(originalStr.getBytes(StandardCharsets.UTF_8), publicKey, sign);
    }

    /**
     * 校验数字签名
     *
     * @param data      需要生成数字签名的源数据
     * @param publicKey 公钥（Base64 编码）
     * @param sign      数字签名
     */
    public static boolean verifySign(byte[] data, String publicKey, String sign) throws Exception {
        // 使用密钥规范（密钥材料）生成公钥对象
        PublicKey pubKey = RSAUtil.getPublicKey(publicKey);
        // 基于指定签名算法，生成数字签名工具类
        Signature signature = Signature.getInstance(SELECTED_ALGORITHM);
        // 初始化公钥
        signature.initVerify(pubKey);
        // 设置待验签数据（源数据）
        signature.update(data);

        // 验证签名是否一致
        return signature.verify(DECODER64.decode(sign));
    }

}
